﻿import random, time, pygame, sys
from pygame.locals import *
from pygame.font import *

FPS = 25  
WINDOWWIDTH = 640  
WINDOWHEIGHT = 480  
BOXSIZE = 20  
BOARDWIDTH = 10  
BOARDHEIGHT = 20  
BLANK = '.'  
  
MOVESIDEWAYSFREQ = 0.15  
MOVEDOWNFREQ = 0.1  
  
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * BOXSIZE) / 2)  
TOPMARGIN = WINDOWHEIGHT - (BOARDHEIGHT * BOXSIZE) - 5  
#               R    G    B  
WHITE       = (247, 247, 247)  
GRAY        = (125, 132, 140)  
BLACK       = (  0,   0,   0)  
RED         = (198,  11,  22)  
LIGHTRED    = (254,  65,  66)  
GREEN       = (  0,  94,  55)  
LIGHTGREEN  = ( 38, 178, 106)  
BLUE        = (  0, 103, 166)  
LIGHTBLUE   = ( 0,  171, 216)  
YELLOW      = (244, 182,   3)  
LIGHTYELLOW = (244, 230,  13)
OTHER       = (210, 65,   63)
  
BORDERCOLOR = GRAY  
BGCOLOR     = BLACK
TEXTCOLOR   = WHITE
SPECIALTEXT = OTHER
TEXTSHADOWCOLOR = GRAY
COLORS      = (     BLUE,      GREEN,      RED,      YELLOW)  
LIGHTCOLORS = (LIGHTBLUE, LIGHTGREEN, LIGHTRED, LIGHTYELLOW)  
assert len(COLORS) == len(LIGHTCOLORS)
  
TEMPLATEWIDTH  = 5  
TEMPLATEHEIGHT = 5  
  
S_SHAPE_TEMPLATE = [['.....',  
                     '.....',  
                     '..OO.',  
                     '.OO..',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '..OO.',  
                     '...O.',  
                     '.....']]  
  
Z_SHAPE_TEMPLATE = [['.....',  
                     '.....',  
                     '.OO..',  
                     '..OO.',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '.OO..',  
                     '.O...',  
                     '.....']]  
  
I_SHAPE_TEMPLATE = [['..O..',  
                     '..O..',  
                     '..O..',  
                     '..O..',  
                     '.....'],  
                    ['.....',  
                     '.....',  
                     'OOOO.',  
                     '.....',  
                     '.....']]  
  
O_SHAPE_TEMPLATE = [['.....',  
                     '.....',  
                     '.OO..',  
                     '.OO..',  
                     '.....']]  
  
J_SHAPE_TEMPLATE = [['.....',  
                     '.O...',  
                     '.OOO.',  
                     '.....',  
                     '.....'],  
                    ['.....',  
                     '..OO.',  
                     '..O..',  
                     '..O..',  
                     '.....'],  
                    ['.....',  
                     '.....',  
                     '.OOO.',  
                     '...O.',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '..O..',  
                     '.OO..',  
                     '.....']]  
  
L_SHAPE_TEMPLATE = [['.....',  
                     '...O.',  
                     '.OOO.',  
                     '.....',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '..O..',  
                     '..OO.',  
                     '.....'],  
                    ['.....',  
                     '.....',  
                     '.OOO.',  
                     '.O...',  
                     '.....'],  
                    ['.....',  
                     '.OO..',  
                     '..O..',  
                     '..O..',  
                     '.....']]  
  
T_SHAPE_TEMPLATE = [['.....',  
                     '..O..',  
                     '.OOO.',  
                     '.....',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '..OO.',  
                     '..O..',  
                     '.....'],  
                    ['.....',  
                     '.....',  
                     '.OOO.',  
                     '..O..',  
                     '.....'],  
                    ['.....',  
                     '..O..',  
                     '.OO..',  
                     '..O..',  
                     '.....']]  
  
PIECES = {'S': S_SHAPE_TEMPLATE,  
          'Z': Z_SHAPE_TEMPLATE,  
          'J': J_SHAPE_TEMPLATE,  
          'L': L_SHAPE_TEMPLATE,  
          'I': I_SHAPE_TEMPLATE,  
          'O': O_SHAPE_TEMPLATE,  
          'T': T_SHAPE_TEMPLATE}

BACKGROUND  = pygame.image.load('background.jpg')
LIGHTGROUND = pygame.image.load('lightground.jpg')
GAMEGROUND  = pygame.image.load('background2.jpg')
GAMEOVER    = pygame.image.load('gameover.jpg')
  
def main():  
    global FPSCLOCK, DISPLAYSURF, BASICFONT, SPECIALFONT, BIGFONT ,SPECIALBIG ,OTHERFONT
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    DISPLAYSURF.blit(LIGHTGROUND, (0,0))

    #字体
    BASICFONT = pygame.font.SysFont('arial', 25)
    SPECIALFONT = pygame.font.SysFont('Lithos Pro Bold',25)
    BIGFONT = pygame.font.SysFont('segoe script', 150)
    SPECIALBIG = pygame.font.SysFont('Lithos Pro Black',35)
    OTHERFONT = pygame.font.SysFont('Rosewood Std Regular',90)
    
    pygame.display.set_caption('俄罗斯方块')
      
    showTextScreen('Tetris')  
    while True:
        if random.randint(0, 1) == 0:  
            pygame.mixer.music.load('Remember (Original Mix) - remix.mp3')  
        else:  
            pygame.mixer.music.load('Ryan Farish - Life in Stereo.mp3')  
        pygame.mixer.music.play(-1, 0.0)  #播放音乐
        runGame()
        pygame.mixer.music.stop()
        DISPLAYSURF.blit(GAMEOVER, (0,0))
        showTextScreen('')
  
def runGame():  
    # 游戏变量：游戏底板、下落刷新速度、分数、等级
    board = getBlankBoard()
    lastMoveDownTime = time.time()  
    lastMoveSidewaysTime = time.time()  
    lastFallTime = time.time()  
    movingDown = False
    movingLeft = False  
    movingRight = False  
    score = 0  
    level, fallFreq = calculateLevelAndFallFreq(score)

    fallingPiece = getNewPiece()  
    nextPiece = getNewPiece()
      
    while True: #游戏循环
        if fallingPiece == None:
            # 如果没有移动的块，在顶部产生一个新的
            fallingPiece = nextPiece  
            nextPiece = getNewPiece()  
            lastFallTime = time.time() # 重置lastFallTime  
            DISPLAYSURF.blit(BACKGROUND, (0,0))
  
            if not isValidPosition(board, fallingPiece):  
                return # 到顶游戏结束
  
        checkForQuit()  
        for event in pygame.event.get(): # 游戏过程循环
            if event.type == KEYUP:  
                if (event.key == K_p):  
                    # 游戏暂停
                    pygame.mixer.music.stop()
                    showOtherScreen('Paused')
                    pygame.mixer.music.play(-1, 0.0)
                    lastFallTime = time.time()
                    lastMoveDownTime = time.time()  
                    lastMoveSidewaysTime = time.time()  
                elif (event.key == K_LEFT or event.key == K_a):  
                    movingLeft = False  
                elif (event.key == K_RIGHT or event.key == K_d):  
                    movingRight = False  
                elif (event.key == K_DOWN or event.key == K_s):  
                    movingDown = False  
  
            elif event.type == KEYDOWN:  
                # 延边界移动
                if (event.key == K_LEFT or event.key == K_a) and isValidPosition(board, fallingPiece, adjX=-1):  
                    fallingPiece['x'] -= 1  
                    movingLeft = True  
                    movingRight = False  
                    lastMoveSidewaysTime = time.time()  
  
                elif (event.key == K_RIGHT or event.key == K_d) and isValidPosition(board, fallingPiece, adjX=1):  
                    fallingPiece['x'] += 1  
                    movingRight = True  
                    movingLeft = False  
                    lastMoveSidewaysTime = time.time()  
  
                # 旋转（有空间）
                elif (event.key == K_UP or event.key == K_w):  
                    fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']])  
                    if not isValidPosition(board, fallingPiece):  
                        fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']])  
                elif (event.key == K_q): # 反向旋转
                    fallingPiece['rotation'] = (fallingPiece['rotation'] - 1) % len(PIECES[fallingPiece['shape']])  
                    if not isValidPosition(board, fallingPiece):  
                        fallingPiece['rotation'] = (fallingPiece['rotation'] + 1) % len(PIECES[fallingPiece['shape']])  
  
                # 下键和s加速下降
                elif (event.key == K_DOWN or event.key == K_s):  
                    movingDown = True  
                    if isValidPosition(board, fallingPiece, adjY=1):  
                        fallingPiece['y'] += 1  
                    lastMoveDownTime = time.time()  
  
                # space直接下降
                elif event.key == K_SPACE:  
                    movingDown = False  
                    movingLeft = False  
                    movingRight = False  
                    for i in range(1, BOARDHEIGHT):  
                        if not isValidPosition(board, fallingPiece, adjY=i):  
                            break  
                    fallingPiece['y'] += i - 1  
  
        # 处理按键带来的移动过程
        if (movingLeft or movingRight) and time.time() - lastMoveSidewaysTime > MOVESIDEWAYSFREQ:  
            if movingLeft and isValidPosition(board, fallingPiece, adjX=-1):  
                fallingPiece['x'] -= 1  
            elif movingRight and isValidPosition(board, fallingPiece, adjX=1):  
                fallingPiece['x'] += 1  
            lastMoveSidewaysTime = time.time()  
  
        if movingDown and time.time() - lastMoveDownTime > MOVEDOWNFREQ and isValidPosition(board, fallingPiece, adjY=1):  
            fallingPiece['y'] += 1  
            lastMoveDownTime = time.time()  
  
        # 正常下落
        if time.time() - lastFallTime > fallFreq:  
            # 检查是否落地  
            if not isValidPosition(board, fallingPiece, adjY=1):  
                # 已落地，成为新陆地
                addToBoard(board, fallingPiece)  
                score += removeCompleteLines(board)  
                level, fallFreq = calculateLevelAndFallFreq(score)  
                fallingPiece = None
            else:  
                # 否则正常下落直到落地
                fallingPiece['y'] += 1  
                lastFallTime = time.time()  
  
        # 显示过程
        DISPLAYSURF.blit(GAMEGROUND, (0,0))
        drawBoard(board)  
        drawStatus(score, level)
        drawNextPiece(nextPiece)
        if fallingPiece != None:  
            drawPiece(fallingPiece)  
  
        pygame.display.update()  
        FPSCLOCK.tick(FPS)  
  

def makeTextObjs(text, font, color):  
    surf = font.render(text, True, color)  
    return surf, surf.get_rect()  
  
  
def terminate():  
    pygame.quit()  
    sys.exit()
     
  
def checkForKeyPress():  
    # 寻找按下键盘的事件
    # 抓取键盘放开的事件
    checkForQuit()  
  
    for event in pygame.event.get([KEYDOWN, KEYUP]):  
        if event.type == KEYDOWN:  
            continue
        return event.key  
    return None  
  
  
def showTextScreen(text):  
    # 画面中央显示文字直到按下按钮
    # 阴影部分
    titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTSHADOWCOLOR)  
    titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))  
    DISPLAYSURF.blit(titleSurf, titleRect)  
  
    # 文字部分
    titleSurf, titleRect = makeTextObjs(text, BIGFONT, TEXTCOLOR)  
    titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3)  
    DISPLAYSURF.blit(titleSurf, titleRect)  
  
    # "Press any key to play" 
    pressKeySurf, pressKeyRect = makeTextObjs('Press any key to play', BASICFONT, SPECIALTEXT)  
    pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100)  
    DISPLAYSURF.blit(pressKeySurf, pressKeyRect)  
  
    while checkForKeyPress() == None:  
        pygame.display.update()  
        FPSCLOCK.tick()


def showOtherScreen(text):  
    # 画面中央显示文字直到按下按钮
    # 其他文字显示
    # 阴影部分
    titleSurf, titleRect = makeTextObjs(text, OTHERFONT, TEXTSHADOWCOLOR)  
    titleRect.center = (int(WINDOWWIDTH / 2) + 3, int(WINDOWHEIGHT / 2) + 3)  
    DISPLAYSURF.blit(titleSurf, titleRect)  
  
    # 文字部分
    titleSurf, titleRect = makeTextObjs(text, OTHERFONT, TEXTCOLOR)  
    titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))  
    DISPLAYSURF.blit(titleSurf, titleRect)  
  
    # "Press any key to play" 
    pressKeySurf, pressKeyRect = makeTextObjs('Press any key to play', BASICFONT, TEXTCOLOR)  
    pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 40)  
    DISPLAYSURF.blit(pressKeySurf, pressKeyRect)  
  
    while checkForKeyPress() == None:  
        pygame.display.update()  
        FPSCLOCK.tick()
  
  
def checkForQuit():
    for event in pygame.event.get(QUIT): # 得到quit指令
        terminate()
    for event in pygame.event.get(KEYUP): # 得到按键信息
        if event.key == K_ESCAPE:  
            terminate() # Esc时退出 
        pygame.event.post(event) # 其余指令不处理
  
  
def calculateLevelAndFallFreq(score):  
    # 根据得分返回等级
    # 等级决定下落频率
    level = int(score / 10) + 1  
    fallFreq = 0.27 - (level * 0.02)  
    return level, fallFreq  

  
def getNewPiece():  
    # 创建一个随机方位随机颜色的随机块
    shape = random.choice(list(PIECES.keys()))  
    newPiece = {'shape': shape,  
                'rotation': random.randint(0, len(PIECES[shape]) - 1),  
                'x': int(BOARDWIDTH / 2) - int(TEMPLATEWIDTH / 2),  
                'y': -2,
                'color': random.randint(0, len(COLORS)-1)}  
    return newPiece  
  
  
def addToBoard(board, piece):  
    # 在底板上显示块
    for x in range(TEMPLATEWIDTH):  
        for y in range(TEMPLATEHEIGHT):  
            if PIECES[piece['shape']][piece['rotation']][y][x] != BLANK:  
                board[x + piece['x']][y + piece['y']] = piece['color']  
  
  
def getBlankBoard():  
    # 创建空底板
    board = []  
    for i in range(BOARDWIDTH):  
        board.append([BLANK] * BOARDHEIGHT)  
    return board  
  
  
def isOnBoard(x, y):  
    return x >= 0 and x < BOARDWIDTH and y < BOARDHEIGHT  
  
  
def isValidPosition(board, piece, adjX=0, adjY=0):  
    # 如果在边界内且无碰撞返回True
    for x in range(TEMPLATEWIDTH):  
        for y in range(TEMPLATEHEIGHT):  
            isAboveBoard = y + piece['y'] + adjY < 0  
            if isAboveBoard or PIECES[piece['shape']][piece['rotation']][y][x] == BLANK:  
                continue  
            if not isOnBoard(x + piece['x'] + adjX, y + piece['y'] + adjY):  
                return False  
            if board[x + piece['x'] + adjX][y + piece['y'] + adjY] != BLANK:  
                return False  
    return True  
  
def isCompleteLine(board, y):  
    # 如果一行内没有空格则返回True
    for x in range(BOARDWIDTH):  
        if board[x][y] == BLANK:  
            return False  
    return True  
  
  
def removeCompleteLines(board):  
    # 移除完成行，并使上面的下移，返回完成行数
    numLinesRemoved = 0  
    y = BOARDHEIGHT - 1 # 开始层
    while y >= 0:  
        if isCompleteLine(board, y):  
            # 移除行并下移
            for pullDownY in range(y, 0, -1):  
                for x in range(BOARDWIDTH):  
                    board[x][pullDownY] = board[x][pullDownY-1]
            for x in range(BOARDWIDTH):  
                board[x][0] = BLANK  
            numLinesRemoved += 1
        else:  
            y -= 1
    return numLinesRemoved  
  
  
def convertToPixelCoords(boxx, boxy):  
    # 改变块坐标
    return (XMARGIN + (boxx * BOXSIZE)), (TOPMARGIN + (boxy * BOXSIZE))  
  
  
def drawBox(boxx, boxy, color, pixelx=None, pixely=None):  
    # 在坐标点产生块
    if color == BLANK:  
        return
    if pixelx == None and pixely == None:  
        pixelx, pixely = convertToPixelCoords(boxx, boxy)  
    pygame.draw.rect(DISPLAYSURF, COLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 1, BOXSIZE - 1))  
    pygame.draw.rect(DISPLAYSURF, LIGHTCOLORS[color], (pixelx + 1, pixely + 1, BOXSIZE - 4, BOXSIZE - 4))  
  
  
def drawBoard(board):
    # 底板边界
    pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (XMARGIN - 3, TOPMARGIN - 7, (BOARDWIDTH * BOXSIZE) + 8, (BOARDHEIGHT * BOXSIZE) + 8), 5)  
  
    # 底板背景色
    pygame.draw.rect(DISPLAYSURF, BGCOLOR, (XMARGIN, TOPMARGIN, BOXSIZE * BOARDWIDTH, BOXSIZE * BOARDHEIGHT))  
    # 在底板上显示块
    for x in range(BOARDWIDTH):  
        for y in range(BOARDHEIGHT):  
            drawBox(x, y, board[x][y])  
  
  
def drawStatus(score, level):  
    # 显示分数
    scoreSurf = SPECIALBIG.render('Score: %s' % score, True, TEXTCOLOR)  
    scoreRect = scoreSurf.get_rect()  
    scoreRect.topleft = (WINDOWWIDTH - 150, 50)  
    DISPLAYSURF.blit(scoreSurf, scoreRect)  
  
    # 显示等级
    levelSurf = SPECIALBIG.render('Level: %s' % level, True, TEXTCOLOR)  
    levelRect = levelSurf.get_rect()  
    levelRect.topleft = (WINDOWWIDTH - 150, 90)  
    DISPLAYSURF.blit(levelSurf, levelRect)  

    guide()
  
def drawPiece(piece, pixelx=None, pixely=None):  
    shapeToDraw = PIECES[piece['shape']][piece['rotation']]  
    if pixelx == None and pixely == None:  
        # 如果pixelx和pixely没有指定，则使用piece中的数据
        pixelx, pixely = convertToPixelCoords(piece['x'], piece['y'])  
  
    # 构成块的每个小方块
    for x in range(TEMPLATEWIDTH):  
        for y in range(TEMPLATEHEIGHT):  
            if shapeToDraw[y][x] != BLANK:  
                drawBox(None, None, piece['color'], pixelx + (x * BOXSIZE), pixely + (y * BOXSIZE))  
  
  
def drawNextPiece(piece):  
    # "next"文本
    nextSurf = SPECIALBIG.render('Next:', True, TEXTCOLOR)  
    nextRect = nextSurf.get_rect()  
    nextRect.topleft = (WINDOWWIDTH - 150, 130)  
    DISPLAYSURF.blit(nextSurf, nextRect)  
    # "next"图案
    drawPiece(piece, pixelx=WINDOWWIDTH-120, pixely=160)

def guide():
    # 左侧说明文字
    gSurf = SPECIALBIG.render('Guidance:', True, OTHER)  
    gRect = gSurf.get_rect()  
    gRect.topleft = (WINDOWWIDTH - 630, 50)  
    DISPLAYSURF.blit(gSurf, gRect)
    tSurf = SPECIALFONT.render('A/Left D/Right', True, TEXTCOLOR)  
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 110)  
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('W/Up to spin', True, TEXTCOLOR)  
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 140)  
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('Q to backspin', True, TEXTCOLOR)  
    tRect = tSurf.get_rect()
    tRect.topleft = (WINDOWWIDTH - 630, 170)
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('S/Down to go down', True, TEXTCOLOR)  
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 200)  
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('Space to accelerate', True, TEXTCOLOR)  
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 230)  
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('P to pause', True, TEXTCOLOR)
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 260)  
    DISPLAYSURF.blit(tSurf, tRect)
    tSurf = SPECIALFONT.render('Esc to quit', True, TEXTCOLOR)
    tRect = tSurf.get_rect()  
    tRect.topleft = (WINDOWWIDTH - 630, 290)  
    DISPLAYSURF.blit(tSurf, tRect)
      
if __name__ == '__main__':  
    main()
